package executor

import (
	"io"

	"github.com/viant/agently/client/conversation"
	"github.com/viant/agently/genai/agent"
	"github.com/viant/agently/genai/elicitation"
	elicrouter "github.com/viant/agently/genai/elicitation/router"
	modelprovider "github.com/viant/agently/genai/llm/provider"
	gtool "github.com/viant/agently/genai/tool"
	approval "github.com/viant/agently/internal/approval"
	mcpcfg "github.com/viant/agently/internal/mcp/config"
	mcpmgr "github.com/viant/agently/internal/mcp/manager"
	meta "github.com/viant/agently/internal/workspace/service/meta"
)

type Option func(config *Service)

// WithNewElicitationAwaiter registers an interactive Awaiter responsible for
// resolving schema-driven user prompts ("elicitation"). When set, the awaiter
// is attached to the internally managed MCP client so that interactive CLI
// sessions can synchronously obtain the required payload.
//
// The last non-nil value wins when the option is applied multiple times.
// WithNewElicitationAwaiter registers (or overrides) the Awaiter that will be used
// to resolve schema-based elicitation requests originating from the runtime.
//
// Passing a non-nil Awaiter enables interactive (or otherwise custom)
// behaviour.  Passing nil explicitly disables any previously registered
// Awaiter – this is useful for headless deployments such as the embedded HTTP
// server where blocking on stdin must be avoided.
//
// The *last* call wins, therefore a later invocation can override an earlier
// one (including the implicit registration performed by the CLI helpers).
func WithNewElicitationAwaiter(newAwaiter func() elicitation.Awaiter) Option {
	return func(s *Service) {
		// Allow nil to reset an earlier registration so that callers like the
		// HTTP server can ensure the executor never blocks on stdin.
		s.newAwaiter = newAwaiter
	}
}

func WithConfig(config *Config) Option {
	return func(s *Service) {
		s.config = config
	}
}

// WithToolDebugLogger enables debug logging for every atool call executed via
// the executor's atool registry. Each invocation is written to the supplied
// writer. Passing nil disables logging.
func WithToolDebugLogger(w io.Writer) Option {
	return func(s *Service) {
		if s.tools == nil && s.mcpMgr != nil {
			if reg, err := gtool.NewDefaultRegistry(s.mcpMgr); err == nil {
				reg.SetDebugLogger(w)
				s.tools = reg
			}
		}
	}
}

// WithMetaService sets metaService service
func WithMetaService(metaService *meta.Service) Option {
	return func(s *Service) {
		if s.config == nil {
			s.config = &Config{}
		}
		s.config.metaService = metaService
	}
}

func WithConversionClient(client conversation.Client) Option {
	return func(s *Service) {
		s.convClient = client
	}
}

// WithWorkflowOptions removed in decoupled mode.

func WithModelConfig(providers ...*modelprovider.Config) Option {
	return func(s *Service) {
		if s.config.Model == nil {
			s.config.Model = &mcpcfg.Group[*modelprovider.Config]{}
		}
		s.config.Model.Items = append(s.config.Model.Items, providers...)
	}

}

// WithAgents sets a custom agent registry.
func WithAgents(agents ...*agent.Agent) Option {
	return func(s *Service) {
		if s.config.Agent == nil {
			s.config.Agent = &mcpcfg.Group[*agent.Agent]{}
		}
		s.config.Agent.Items = append(s.config.Agent.Items, agents...)
	}
}

// WithTools sets a custom atool registry.
func WithTools(tools gtool.Registry) Option {
	return func(s *Service) {
		s.tools = tools
	}
}

// WithApprovalService sets an internal approval service implementation.
// Passing nil clears any existing service.
func WithApprovalService(svc approval.Service) Option {
	return func(s *Service) { s.approvalSvc = svc }
}

// WithoutHotSwap disables automatic workspace hot-reload. Use this option for
// deterministic production runs where configuration should not change while
// the process is running.
func WithoutHotSwap() Option {
	return func(s *Service) {
		s.hotSwapDisabled = true
	}
}

// WithMCPManager attaches a per-conversation MCP client manager so that tool
// executions can inject the appropriate client/token at call time.
func WithMCPManager(m *mcpmgr.Manager) Option {
	return func(s *Service) {
		s.mcpMgr = m
		// Tool will be created with manager when needed (see WithToolDebugLogger or explicit WithTools).
	}
}

// WithElicitationRouter injects a shared router used by the agent to wait on
// assistant-originated elicitations. In server mode, pass the same instance as
// the HTTP handler uses so UI callbacks unblock the agent loop.
func WithElicitationRouter(r elicrouter.ElicitationRouter) Option {
	return func(s *Service) {
		// store on executor; passed to agent.New in registerServices
		if s != nil {
			// no dedicated field yet; attach via config meta to avoid API churn
			// better: add a field on Service; doing that now.
		}
		s.elicitationRouter = r
	}
}
